#include <cmath>
#include "cache.h"
#include "config.h"
using namespace std;
const int hitLatency=1;
const int snoopLatency=3;
const int adjacentLatency=1;
const int memoryLatency=100;
int maxLatency=0;
int debug_way;
void  checkMax(int x)
{
    if(x> maxLatency)
        maxLatency=x;
}

int main(int argc, char** argv)
{
    int totalLatency=0;
    //contents to be read from config file
    int DATALINE,DATAWAY,data_index,data_offset,data_tag,data_bytes,addressbits,protocol,numCaches;
    string trace;
    // read configuration file
    try
    {
        const char* cfgFilename = argv[1];
        Config cfg(cfgFilename);
        DATALINE=cfg.get("datasets");
        DATAWAY=cfg.get("dataways");
        addressbits=cfg.get("address-size");
        data_bytes=cfg.get("databytes");
        string trac=cfg.get("tracefile");
        protocol=cfg.get("protocol");
        numCaches=cfg.get("numCaches");

        trace=trac;
    }
    catch (exception& e)
    {
        cerr << e.what() << endl;
        return -1;
    }

    //delete files from previous simulatons
    remove("L2_operations.txt");
    remove("stats.din");

    //find index,offset,tag_bits for caches
    data_index=log2(DATALINE);
    data_offset=log2(data_bytes);
    data_tag=addressbits-data_index-data_offset;

    // initialize the counters
    int drefcounter = 0;
    int dreadcounter= 0;
    int dwritecounter = 0;
    int dhitcounter = 0;
    int dmisscounter = 0;
    int dhitM = 0;
    int dhit = 0;
    int dway;


    // initialize the input from the tracefile
    int n;
    int op;
    unsigned long int addr;
    // L1 data ana instruction objects
    Cache *snoopyCache[numCaches];
    for(int i=0; i<numCaches; i++)
    {
        snoopyCache[i]=new Cache(DATALINE,DATAWAY);
        snoopyCache[i]->init(protocol);

    }
    // initialize the LRU bits to the way, this function also initialize tag and validbits
    // open the tracefile
    ifstream in(trace.c_str());
    // set it up to read line by line
    // set n and addr accordingly
    while (1)
    {
        in>>n;
        in>>op;
        if(op!=8&&op!=9)
            in>>hex>>addr;
        if(in.eof())
            break;

        switch(op)
        {
            // the processor  makes a read request
            // n = 0 read data request from L1 cache
        case 0:
        {
            drefcounter++;
            dreadcounter++;

            int tag = addr >> data_tag;
            int index = (addr >> data_offset) & (DATALINE-1);
            int byteSelect = addr & (data_bytes-1) ;
            // determine whether this tag exists in the cache and, if so, which way

            dway = snoopyCache[n]->checkTag(index, tag);
            // if the tag exists
            if (0<=dway&&dway<DATAWAY)
            {
                // this tag exists ,check valid bit
                int valid = snoopyCache[n]->cache[index][dway].validbit;
                if (valid==1)
                {
                    dhitcounter++;
                    // update the LRU to set 'way' to least recently used
                    snoopyCache[n]->updateLRU(index, dway);
                    // copy the addr to address  for case 9 display
                    snoopyCache[n]->cache[index][dway].address = addr;
                    totalLatency+=hitLatency;
                }
                // this tag exists but it's been invalidated and can't be used
                else
                {
                    dmisscounter++;
                    // update LRU
                    snoopyCache[n]->updateLRU(index, dway);
                    //set valid bit
                    snoopyCache[n]->cache[index][dway].address = addr;
                    int other_way;
                    int valid=0;
                    int latency=0;
                    int snoop_count=0;
                    for(int i=0; i<numCaches; i++)
                    {


                        if(i!=n)
                        {
                            other_way=snoopyCache[i]->checkTag(index,tag);
                            if(snoopyCache[n]->cache[index][dway].state!=E)
                            {
                                snoop_count++;
                                totalLatency+=snoopLatency;
                            }
                            if(0<=other_way&&other_way<DATAWAY)
                                valid=snoopyCache[i]->snoop(index, other_way);
                        }
                        if(valid==1&&i!=n)
                        {
                            snoopyCache[i]->update(op+2,index,other_way,valid);
                            if(latency > (abs(n-i)*adjacentLatency)|| latency ==0)
                                latency=abs(n-i)*adjacentLatency;
                        }
                    }
                    if(valid==1)
                    {
                        totalLatency+=latency;
                        checkMax(latency+(snoop_count*snoopLatency));
                    }
                    else
                    {
                        totalLatency+=memoryLatency;
                        checkMax(memoryLatency+(snoop_count*snoopLatency));
                    }
                    snoopyCache[n]->update(op,index,dway,valid);
                }
            }
            // this tag simply doesn't exist in the cache in any form
            else
            {
                dmisscounter++;
                // use the LRU bit to determine which way to evict
                dway = snoopyCache[n]->checkLRU(index);
                // update LRU
                snoopyCache[n]->updateLRU(index, dway);
                snoopyCache[n]->cache[index][dway].tag = tag;
                snoopyCache[n]->cache[index][dway].address = addr;
                int other_way;
                int valid=0;
                int latency=0;
                int snoop_count=0;
                for(int i=0; i<numCaches; i++)
                {

                    if(i!=n)
                    {
                        other_way=snoopyCache[i]->checkTag(index,tag);
                        if(snoopyCache[n]->cache[index][dway].state!=E)
                        {
                            snoop_count++;
                            totalLatency+=snoopLatency;
                        }
                        if(0<=other_way&&other_way<DATAWAY)

                            valid=snoopyCache[i]->snoop(index, other_way);

                    }
                    if(valid==1&&i!=n)
                    {
                        snoopyCache[i]->update(op+2,index,other_way,valid);
                        if(latency > (abs(n-i)*adjacentLatency)|| latency ==0)
                            latency=abs(n-i)*adjacentLatency;


                    }
                }
                debug_way=other_way;
                if(valid==1)
                {
                    totalLatency+=latency;
                    checkMax(latency+(snoop_count*snoopLatency));
                }
                else
                {
                    totalLatency+=memoryLatency;
                    checkMax(memoryLatency+(snoop_count*snoopLatency));
                }
                snoopyCache[n]->update(op,index,dway,valid);

            }
        }
        break;
        // 1 write data request from L1 cache
        case 1:
        {
            drefcounter++;
            dwritecounter++;
            int tag = addr >> data_tag;
            int index = (addr >> data_offset) & (DATALINE-1);
            int byteSelect = addr & (data_bytes-1) ;
            // determine whether this tag exists in the cache and, if so, which way
            dway = snoopyCache[n]->checkTag(index, tag);
            // if the tag exists
            if (0<=dway&&dway<DATAWAY)
            {
                // this tag exists so check if it's valid
                int validbit = snoopyCache[n]->cache[index][dway].validbit;
                if (validbit==1)
                {
                    dhitcounter++;
                    // update the LRU to set 'way' to least recently used
                    snoopyCache[n]->updateLRU(index, dway);
                    // write back to L2

                    snoopyCache[n]->cache[index][dway].address = addr;
                    int other_way;
                    int valid=0;
                    int snoop_count=0;
                    for(int i=0; i<numCaches; i++)
                    {

                        if(i!=n)
                        {
                            other_way=snoopyCache[i]->checkTag(index,tag);
                            if(snoopyCache[n]->cache[index][dway].state!=E)
                            {
                                snoop_count++;
                                totalLatency+=snoopLatency;
                            }

                            if(0<=other_way&&other_way<DATAWAY)
                            {
                                valid=snoopyCache[i]->snoop(index, other_way);
                            }
                        }
                        if(valid==1&&i!=n)
                        {
                            snoopyCache[i]->update(op+2,index,other_way,valid);
                        }
                    }
                    snoopyCache[n]->update(op,index,dway,valid);
                    totalLatency+=hitLatency;

                }
                // in this case we have to get the data from L1 and expand it to 64 bytes
                else
                {
                    dmisscounter++;
                    // update LRU

                    snoopyCache[n]->updateLRU(index, dway);

                    snoopyCache[n]->cache[index][dway].address = addr;
                    int other_way;
                    int valid=0;
                    int latency = 0;
                    int snoop_count=0;
                    for(int i=0; i<numCaches; i++)
                    {

                        if(i!=n)
                        {
                            other_way=snoopyCache[i]->checkTag(index,tag);
                            if(snoopyCache[n]->cache[index][dway].state!=E)
                            {
                                snoop_count++;
                                totalLatency+=snoopLatency;
                            }

                            if(0<=other_way&&other_way<DATAWAY)
                            {
                                valid=snoopyCache[i]->snoop(index, other_way);
                            }
                        }
                        if(valid==1&&i!=n)
                        {
                            snoopyCache[i]->update(op+2,index,other_way,valid);
                            if(latency > (abs(n-i)*adjacentLatency)|| latency ==0)
                                latency=abs(n-i)*adjacentLatency;
                        }
                    }
                    if(valid==1)
                    {
                        totalLatency+=latency;
                        checkMax(latency+(snoop_count*snoopLatency));
                    }
                    else
                    {
                        totalLatency+=memoryLatency;
                        checkMax(memoryLatency+(snoop_count*snoopLatency));
                    }
                    snoopyCache[n]->update(op,index,dway,valid);

                }
            }
            // this tag simply doesn't exist in the cache in any form
            else
            {
                dmisscounter++;
                // use the LRU bit to determine which way to evict
                dway = snoopyCache[n]->checkLRU(index);

                snoopyCache[n]->cache[index][dway].tag = tag;

                // update LRU
                snoopyCache[n]->updateLRU(index, dway);
                snoopyCache[n]->cache[index][dway].address = addr;
                int other_way;
                int valid=0;
                int latency = 0;
                int snoop_count=0;
                for(int i=0; i<numCaches; i++)
                {
                    if(i!=n)
                    {

                        other_way=snoopyCache[i]->checkTag(index,tag);
                        if(snoopyCache[n]->cache[index][dway].state!=E)
                        {
                            snoop_count++;
                            totalLatency+=snoopLatency;
                        }

                        if(0<=other_way&&other_way<DATAWAY)
                            valid=snoopyCache[i]->snoop(index, other_way);
                    }
                    if(valid==1&&i!=n)
                    {
                        snoopyCache[i]->update(op+2,index,other_way,valid);
                        if(latency > (abs(n-i)*adjacentLatency)|| latency ==0)
                            latency=abs(n-i)*adjacentLatency;
                    }
                }
                if(valid==1)
                {
                    totalLatency+=latency;
                    checkMax(latency+(snoop_count*snoopLatency));
                }
                else
                {
                    totalLatency+=memoryLatency;
                    checkMax(memoryLatency+(snoop_count*snoopLatency));
                }
                snoopyCache[n]->update(op,index,dway,valid);

            }

        }
        break;
        // 9 print cache status
        case 9:
        {
            // Print contents of both caches

            ofstream out2("stats.din", ios::app);
            out2<<"******************************************************\n";
            out2<<"At reference #"<<drefcounter<<", the L1 data cache for processor "<<n<<" looked like this.\n\n";

            for (int index = 0; index < DATALINE; index++)
            {
                for (int way = 0; way < DATAWAY; way++)
                {
                    if (snoopyCache[n]->cache[index][way].validbit != 0)
                    {
                        out2<<"Index "<<index<<" way "<<way<<" tag "<<snoopyCache[n]->cache[index][way].tag<<" LRUbits "<<snoopyCache[n]->cache[index][way].lruBits<< " CC Protocol state "<<snoopyCache[n]->cache[index][way].state<<"\n\n";
                        out2<<"Index "<<index<<" way "<<way<<" tag "<<snoopyCache[n]->cache[index][way].tag<<" LRUbits "<<snoopyCache[n]->cache[index][way].lruBits<< " CC Protocol state "<<snoopyCache[0]->cache[index][debug_way].state<<"\n\n";

                    }
                }
            }
            /*
            	out2<<"******************************************************\n";
            	out2<<"At reference #"<<irefcounter<<", the L1 inst cache looked like this.\n\n";

                    for (int index = 0; index < INSTLINE; index++)
                    {
                       for (int way = 0; way < INSTWAY; way++)
                       {
                           if (instcache->cache[index][way].validBit != 0)
                           {
                              out2<<"Index "<<index<<" way "<<way<<" tag "<<instcache->cache[index][way].tag<<" LRUbits "<<instcache->cache[index][way].lruBits<<"\n\n";
                           }
                       }
                    }
                     out2.close();
            */
        }
        break;
        // end switch statement
        }
    }

    // print the counters in a neat form here at the end of each loop for testing
    float dhitRatio = (float) dhitcounter / drefcounter;
    cout<<"L1 data stats\n";
    cout<<" Total References: "<<drefcounter<<"\n Reads:"<<dreadcounter<<"\n Writes: "<<dwritecounter<<"\n Hits: "<<dhitcounter<<"\n Misses "<<dmisscounter<<"\n Hit ratio: "<<dhitRatio<<"\n";
    cout<<"\n Minimum response time: "<<1<<"\n Average Response Time:"<<float(totalLatency)/drefcounter<<"\n Maximum Response Time:"<<maxLatency;
    return 0;
}
